package ppc.remoteguard;

import java.io.IOException;

import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.PushSourceStream;
import javax.media.protocol.SourceTransferHandler;
import javax.media.rtp.OutputDataStream;
import javax.media.rtp.RTPConnector;

/**
 * Classe che implementa un RTPConnector per gestire la comunicazione RTP
 * attraverso il canale udp dell'applicazione.
 * 
 * <br><br>License: 	GNU General Public License<br>
 * 
 * @author Pier Paolo Ciarravano
 * @version Vers. 0.98 (29/09/2009)
 */
public class RTPMultiplexerConnector implements RTPConnector
{

	SockInputStream dataInStrm = null, ctrlInStrm = null;
	SockOutputStream dataOutStrm = null, ctrlOutStrm = null;

	private UDPMultiplexer udpMultiplexer;
	private int idChannel1;
	private int idChannel2;
	private int idClient;

	public RTPMultiplexerConnector(UDPMultiplexer udpMultiplexer, int idClient, int idChannel1, int idChannel2)
	{
		this.udpMultiplexer = udpMultiplexer;
		this.idClient = idClient;
		this.idChannel1 = idChannel1;
		this.idChannel2 = idChannel2;
	}

	/**
	 * Returns an input stream to receive the RTP data.
	 */
	public PushSourceStream getDataInputStream() throws IOException
	{
		if (dataInStrm == null)
		{
			dataInStrm = new SockInputStream(idChannel1);
			dataInStrm.start();
		}
		return dataInStrm;
	}

	/**
	 * Returns an output stream to send the RTP data.
	 */
	public OutputDataStream getDataOutputStream() throws IOException
	{
		if (dataOutStrm == null)
			dataOutStrm = new SockOutputStream(idChannel1);
		return dataOutStrm;
	}

	/**
	 * Returns an input stream to receive the RTCP data.
	 */
	public PushSourceStream getControlInputStream() throws IOException
	{
		if (ctrlInStrm == null)
		{
			ctrlInStrm = new SockInputStream(idChannel2);
			ctrlInStrm.start();
		}
		return ctrlInStrm;
	}

	/**
	 * Returns an output stream to send the RTCP data.
	 */
	public OutputDataStream getControlOutputStream() throws IOException
	{
		if (ctrlOutStrm == null)
			ctrlOutStrm = new SockOutputStream(idChannel2);
		return ctrlOutStrm;
	}

	/**
	 * Close all the RTP, RTCP streams.
	 */
	public void close()
	{
		if (dataInStrm != null)
			dataInStrm.kill();
		if (ctrlInStrm != null)
			ctrlInStrm.kill();
		// dataSock.close();
		// ctrlSock.close();
	}

	/**
	 * Set the receive buffer size of the RTP data channel. This is only a hint
	 * to the implementation. The actual implementation may not be able to do
	 * anything to this.
	 */
	public void setReceiveBufferSize(int size) throws IOException
	{
		// dataSock.setReceiveBufferSize(size);
	}

	/**
	 * Get the receive buffer size set on the RTP data channel. Return -1 if the
	 * receive buffer size is not applicable for the implementation.
	 */
	public int getReceiveBufferSize()
	{
		// try {
		// return dataSock.getReceiveBufferSize();
		// } catch (Exception e) {
		// return -1;
		// }
		return -1;
	}

	/**
	 * Set the send buffer size of the RTP data channel. This is only a hint to
	 * the implementation. The actual implementation may not be able to do
	 * anything to this.
	 */
	public void setSendBufferSize(int size) throws IOException
	{
		// dataSock.setSendBufferSize(size);
	}

	/**
	 * Get the send buffer size set on the RTP data channel. Return -1 if the
	 * send buffer size is not applicable for the implementation.
	 */
	public int getSendBufferSize()
	{
		// try {
		// return dataSock.getSendBufferSize();
		// } catch (Exception e) {
		// return -1;
		// }
		return -1;
	}

	/**
	 * Return the RTCP bandwidth fraction. This value is used to initialize the
	 * RTPManager. Check RTPManager for more detauls. Return -1 to use the
	 * default values.
	 */
	public double getRTCPBandwidthFraction()
	{
		return -1;
	}

	/**
	 * Return the RTCP sender bandwidth fraction. This value is used to
	 * initialize the RTPManager. Check RTPManager for more detauls. Return -1
	 * to use the default values.
	 */
	public double getRTCPSenderBandwidthFraction()
	{
		return -1;
	}

	/**
	 * An inner class to implement an OutputDataStream based on UDP sockets.
	 */
	class SockOutputStream implements OutputDataStream
	{

		int idChannel;

		public SockOutputStream(int idChannel)
		{
			this.idChannel = idChannel;

		}

		public int write(byte data[], int offset, int len)
		{
			try
			{

				UDPPacket udpPacket = new UDPPacket(idClient, this.idChannel, 0, data, offset, len);

				udpMultiplexer.sendUDPPacket(udpPacket);

			}
			catch (Exception e)
			{
				return -1;
			}
			return len;
		}
	}

	/**
	 * An inner class to implement an PushSourceStream based on UDP sockets.
	 */
	class SockInputStream extends Thread implements PushSourceStream
	{

		int idChannel;

		boolean done = false;
		boolean dataRead = false;

		SourceTransferHandler sth = null;

		public SockInputStream(int idChannel)
		{
			this.idChannel = idChannel;
		}

		public int read(byte buffer[], int offset, int length)
		{

			int result = -1;

			try
			{

				UDPPacket udpPacket = udpMultiplexer.getUDPPacketFromQueue(idClient, this.idChannel);
				result = udpPacket.getDataLength();

				if (length < result)
				{
					throw new RuntimeException("*** ERRORE LENGHT MINORE DELLA LUNGHEZZA PACCHETTO RICEVUTO!!");
				}

				byte[] dataPacket = udpPacket.getData();
				for (int i = offset; i < offset + result; i++)
				{
					buffer[i] = dataPacket[i - offset];
				}

			}
			catch (Exception e)
			{
				return -1;
			}
			synchronized (this)
			{
				dataRead = true;
				notify();
			}
			// return p.getLength();
			return result;
		}

		public synchronized void start()
		{
			super.start();
			if (sth != null)
			{
				dataRead = true;
				notify();
			}
		}

		public synchronized void kill()
		{
			done = true;
			notify();
		}

		public int getMinimumTransferSize()
		{
			return 2 * 1024; // twice the MTU size, just to be safe.
		}

		public synchronized void setTransferHandler(SourceTransferHandler sth)
		{
			this.sth = sth;
			dataRead = true;
			notify();
		}

		// Not applicable.
		public ContentDescriptor getContentDescriptor()
		{
			return null;
		}

		// Not applicable.
		public long getContentLength()
		{
			return LENGTH_UNKNOWN;
		}

		// Not applicable.
		public boolean endOfStream()
		{
			return false;
		}

		// Not applicable.
		public Object[] getControls()
		{
			return new Object[0];
		}

		// Not applicable.
		public Object getControl(String type)
		{
			return null;
		}

		/**
		 * Loop and notify the transfer handler of new data.
		 */
		public void run()
		{
			while (!done)
			{

				synchronized (this)
				{
					while (!dataRead && !done)
					{
						try
						{
							wait();
						}
						catch (InterruptedException e)
						{
						}
					}
					dataRead = false;
				}

				if (sth != null && !done)
				{
					sth.transferData(this);
				}
			}
		}
	}
}
